
/**
 * Unit test framework for Javascript
 * @module test
 *
 */
(function(){

	/**
	 * TestSuit is a sequence of test case. The test case will be run one by one
	 * and the result will be saved to a list of passed or failed based on the 
	 * result.</br>
	 * TestSuilt also provide a console to show messages during testing.
	 * @class TestSuit
	 * @namespace Test
	 */
	var TestSuilt = G.GClass('Test', 'TestSuit');

	/**
	 * Constructure of TestSuilt
	 * 
	 * @method __init
	 * @param title {string} name of the TestSuit
	 * @param container{string/DOM Element} the container to show the test console
	 */
	G.Test.TestSuit.prototype.__init = function(title, container) {
		this.title = title;
		this.testCaseList = [];
		this.passed = [];
		this.failed = [];

		this.divConsole = null;
		this.stopOnError = false;

		this.init_console(container);
	}

	/**
	 * Create a div for test message output, a list for passed case and a list 
	 * for failed.
	 * @method init_console
	 */
	G.Test.TestSuit.prototype.init_console = function(container) 
	{
		if (typeof(container) == 'string') {
			container = document.getElementById(container);
		}

		if (!container) {
			return;
		}
		this.divConsole = document.createElement("div");
		this.divConsole.className = "TEST_CONSOLE";
		container.appendChild(this.divConsole);
	}

	/**
	 * Add a test case to test suit.
	 * @function add_case
	 * @param testCase {object} instance of a TestCase
	 */
	G.Test.TestSuit.prototype.add_case = function(testCase) {
		this.testCaseList.push(testCase);
	}

	/**
	 * Clear the message box and passed/failed list.
	 * @function clear_msg
	 */
	G.Test.TestSuit.prototype.clear_msg = function() 
	{
		this.divConsole.inerHTML = "";

		this.passed = [];
		this.failed = [];
	}

	G.Test.TestSuit.prototype._add_passed = function(testCase) 
	{
		this.passed.push(testCase);
	}

	G.Test.TestSuit.prototype._add_failed = function(testCase) 
	{
		this.failed.push(testCase);
	}

	/**
	 * Create a new log in message box.
	 * @function log
	 * @param msg <string> text of message
	 */
	G.Test.TestSuit.prototype.log = function(msg) 
	{
		var b = document.createElement("b");
		b.className = "LOG";
		b.innerHTML = msg + "</br>";
		this.divConsole.appendChild(b);
	}

	/**
	 * Create a new error message in message box
	 * @function err
	 * @param msg <string> text of error message
	 */
	G.Test.TestSuit.prototype.err = function(msg) 
	{
		var b = document.createElement("b");
		b.className = "ERR";
		b.innerHTML = msg + "</br>";
		this.divConsole.appendChild(b);
	}


	G.Test.TestSuit.prototype.__step = function()
	{
		var i = this.currentCase;
		if (i < this.testCaseList.length) {
			var cs = this.testCaseList[i];
			this.log("Run case: " + cs.title + " ...");
			var _self = this;

			setTimeout(function() {
				var dat = cs.init(); 

				if (_self.stopOnError) {
					cs.run();
					_self.log("ok");
					_self._add_passed(cs);
				} else {
					try {
						cs.run();
						_self.log("ok");
						_self._add_passed(cs);
					} catch (e){
						_self.err("Failed: " + e.message);
						console.log(e);
						_self._add_failed(cs);
					}
				}
				_self.currentCase += 1;
				_self.__step();
			}, 10);
		} else {
			this.log("=========================================");
			var len = this.failed.length;
			if (len > 0) {
				this.err("Failed on:");
				for (var i = 0; i < len; i++) {
					this.err(this.failed[i].title);
				}
			} else {
				this.log("Failed: None.");
			}
		}
	}


	/**
	 * Run the test suit.
	 * @function run
	 */
	G.Test.TestSuit.prototype.run = function() 
	{
		this.currentCase = 0;
		this.__step();
	}

	
	/**
	 * TestCase belong to a TestSuit. TestSuit run the case one by one by call 
	 * it's init() and run() function.  assert() function will check the result
	 * and terminate the test case on failed.
	 *
	 * @class TestCase
	 */
	TestCase = G.GClass('Test', 'TestCase', function(title, init, run, expect) {
			this.__init(title, init, run, expect);
	});


	/**
	 * @function __init
	 */
	TestCase.prototype.__init = function(title, init, run, expect) {
		this.title = title;

		this.init = init || function(){return null};
		this.run = run || function(){return "ok"};
		this.expect = expect || "ok";
	};


	/**
	 * Check value the expression and terminate the current test case if the value is 
	 * not true
	 * @function assert
	 * @param exp <string> the expression to check the value true or false
	 * @param msg <string> the error message that will show on error.
	 */
	G.Test.TestCase.prototype.assert = function(exp, msg)
	{
		if (!exp) throw new Error(msg);	
	}

	/**
	 * Check if the two value are equal, and terminate the test case if it's not.
	 * @function assert_equ
	 */
	G.Test.TestCase.prototype.assert_equ = function(result, expect, msgHead) {
		var msgHead = msgHead || "";
		var msg = msgHead + " Expect:[" + expect + "] got [" + result + "]";
		this.assert(result==expect, msg);
	}

	G.Test.TestCase.prototype.assert_neq = function(result, expect, msgHead) {
		var msgHead = msgHead || "";
		var msg = msgHead + " Got unexpected result[" + result + "]";
		this.assert(result!=expect, msg);
	}

})();


